package com.guaoran.source.spring.framework.webmvc.servlet;

import com.guaoran.source.spring.framework.annotation.Controller;
import com.guaoran.source.spring.framework.annotation.RequestMapping;
import com.guaoran.source.spring.framework.annotation.RequestParam;
import com.guaoran.source.spring.framework.aop.AopProxyUtils;
import com.guaoran.source.spring.framework.context.ApplicationContext;
import com.guaoran.source.spring.framework.webmvc.HandlerAdapter;
import com.guaoran.source.spring.framework.webmvc.HandlerMapping;
import com.guaoran.source.spring.framework.webmvc.ModelAndView;
import com.guaoran.source.spring.framework.webmvc.ViewResolver;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author : 孤傲然
 * @Description :
 * @date :2018/6/18 18:15
 */
public class DispatcherServlet extends HttpServlet {
    private  final String LOCATION = "contextConfigLocation";
    /** List of HandlerMappings used by this servlet */
    /** spring加载HandlerMapping组件时，用来存储Controller的url和对应的方法 */
    /** TODO HandlerMapping 说是SpringMVC最核心的设计，也是最经典的设计，需要研究下*/
    private List<HandlerMapping> handlerMappings = new ArrayList<HandlerMapping>(0);

    /** List of HandlerAdapters used by this servlet */
    private Map<HandlerMapping,HandlerAdapter> handlerAdapters = new HashMap<HandlerMapping,HandlerAdapter>();
    private List<ViewResolver> viewResolvers = new ArrayList<ViewResolver>();
    @Override
    public void init(ServletConfig config) throws ServletException {
        //初始化容器
        ApplicationContext context = new ApplicationContext(config.getInitParameter(LOCATION));
        //加载九大组件
        initStrategies(context);
    }

    /**
     * 九大组件
     * @param context
     */
    private void initStrategies(ApplicationContext context) {
        //有九种策略
        // 针对于每个用户请求，都会经过一些处理的策略之后，最终才能有结果输出
        // 每种策略可以自定义干预，但是最终的结果都是一致
        // =============  这里说的就是传说中的九大组件 ================
        initMultipartResolver(context);//文件上传解析，如果请求类型是multipart将通过MultipartResolver进行文件上传解析
        initLocaleResolver(context);//本地化解析
        initThemeResolver(context);//主题解析

        /** 我们自己会实现 */
        //GPHandlerMapping 用来保存Controller中配置的RequestMapping和Method的一个对应关系
        initHandlerMappings(context);//通过HandlerMapping，将请求映射到处理器
        /** 我们自己会实现 */
        //HandlerAdapters 用来动态匹配Method参数，包括类转换，动态赋值
        initHandlerAdapters(context);//通过HandlerAdapter进行多类型的参数动态匹配

        initHandlerExceptionResolvers(context);//如果执行过程中遇到异常，将交给HandlerExceptionResolver来解析
        initRequestToViewNameTranslator(context);//直接解析请求到视图名

        /** 我们自己会实现 */
        //通过ViewResolvers实现动态模板的解析
        //自己解析一套模板语言
        initViewResolvers(context);//通过viewResolver解析逻辑视图到具体视图实现

        initFlashMapManager(context);//flash映射管理器
    }

    private void initFlashMapManager(ApplicationContext context) {}
    private void initRequestToViewNameTranslator(ApplicationContext context) {}
    private void initHandlerExceptionResolvers(ApplicationContext context) {}
    private void initThemeResolver(ApplicationContext context) {}
    private void initLocaleResolver(ApplicationContext context) {}
    private void initMultipartResolver(ApplicationContext context) {}


    /**
     * 将Controller中配置的RequestMapping和Method进行一一对应
     * @param context
     */
    private void initHandlerMappings(ApplicationContext context) {
        //获取所有的beanDefinition，即从IOC容器中获取
        String[] beanNames = context.getBeanDefinitionNames();
        try {
            for (String beanName:beanNames) {
                //aop的代理类
                Object aopProxy = context.getBean(beanName);
                //原始类：通过代理工具，获得代理类的原始类，因为如果不是原始类的话，注解会拿不到，
                Object controller = AopProxyUtils.getTargetObject(aopProxy);
                Class<?> clazz = controller.getClass();
                //判断是否是Controller,是的话取Controller和method的RequestMapping,拼接请求地址
                String baseUrl = "";
                if(!clazz.isAnnotationPresent(Controller.class)){continue;}
                if(clazz.isAnnotationPresent(RequestMapping.class)){
                    RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                    baseUrl = requestMapping.value();
                }
                //获得所有的方法，并将带有RequestMapping的方法的地址路径拼接起来，并存储到HandlerMapping中
                Method[] methods = clazz.getMethods();
                for (Method method:methods) {
                    if(method.isAnnotationPresent(RequestMapping.class)){
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        String regex = ("/" + baseUrl + "/" +requestMapping.value()).replaceAll("/+","/");
                        Pattern pattern = Pattern.compile(regex);
                        handlerMappings.add(new HandlerMapping(controller,method,pattern));
                        System.out.println("Mapping-->"+regex+":"+method);
                    }

                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * HandlerAdapters 用来动态匹配Method参数，包括类转换，动态赋值
     * @param context
     */
    private void initHandlerAdapters(ApplicationContext context) {
        //在初始化阶段，将这些参数的名字和类型按照一定的顺序存储下来
        //在后面用反射调用的时候，传的参数是一个数组
        //通过记录每个参数的顺序，挨个从数组中进行赋值。这样就可以保证参数的顺序了
        for (HandlerMapping handlerMapping:this.handlerMappings) {
            //每个方法都有一个参数列表，这里保存的是形参列表
            Map<String,Integer> paramMapping = new HashMap<String,Integer>();
            //这里存储的是命名参数
            Annotation[][] parameterAnnotations = handlerMapping.getMethod().getParameterAnnotations();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                for (Annotation an : parameterAnnotations[i]) {
                    if(an instanceof RequestParam){
                        String paramName = ((RequestParam) an).value();
                        if(!"".equals(paramName.trim())){
                            paramMapping.put(paramName,i);
                        }
                    }
                }
            }
            //非命名参数
            //request、reqsponse
            Class<?>[] paramTypes = handlerMapping.getMethod().getParameterTypes();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                Class<?> type = paramTypes[i];
                if(type == HttpServletResponse.class
                        || type == HttpServletRequest.class){
                    paramMapping.put(type.getName(),i);
                }
                
            }
            this.handlerAdapters.put(handlerMapping,new HandlerAdapter(paramMapping));
        }
    }

    /**
     * 通过ViewResolvers实现动态模板的解析
     * 通过viewResolver解析逻辑视图到具体视图实现
     * @param context
     */
    private void initViewResolvers(ApplicationContext context) {
        String templateRoot = context.getConfig().getProperty("templateRoot");
        String templatePath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
        File templateRootFileDir = new File(templatePath);
        for (File template :templateRootFileDir.listFiles()) {
            this.viewResolvers.add(new ViewResolver(template.getName(),template));
        }

    }




    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doService(req,resp);
    }

    /**
     * MVC执行方法入口
     * @param req
     * @param resp
     */
    private void doService(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        try {
            doDispatch(req,resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Exception:Details:\r\n"+e);
            e.printStackTrace();
        }
    }
    /**
     * MVC执行方法入口
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        //根据请求的url，知道找到对应的HandlerMapping，
        HandlerMapping handler = getHandler(req);
        if(handler == null){
            resp.getWriter().write("404 HandlerMapping not Found ");
            return ;
        }
        //根据HandlerMapping里存储的请求的方法，用来找到对应的参数类型，和请求参数中的参数类型进行动态匹配
        HandlerAdapter ha = getHandlerAdapter(handler);
        //HandlerAdapter,对请求参数中的参数类型进行动态匹配,处理完请求方法的参数类型后，进行反射调用对应的方法，并最终返回ModelAndView
        ModelAndView mv = ha.handle(req,resp,handler);
        //最后，经过处理后返回的ModelAndView进行视图解析，并最终将返回结果writer到对应的页面中。
        processDispatchResult(req,resp,mv);

    }

    /**
     * 根据客户端的请求，获得url地址和对应方法的地址
     * @param request
     */
    private HandlerMapping getHandler(HttpServletRequest request) {
        if(this.handlerMappings.isEmpty()){
            return null;
        }
        String url = request.getRequestURI();
        String contextPath = request.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");
        for (HandlerMapping handler :this.handlerMappings) {
            Matcher matcher = handler.getPattern().matcher(url);
            if(!matcher.matches()){continue;}
            return handler;
        }
        return null;
    }
    /**
     * 根据HandlerMapping进行解析，根据方法的参数进行动态匹配，能够自动转型，获得HandlerAdapter
     * @param handler
     * @return
     */
    private HandlerAdapter getHandlerAdapter(HandlerMapping handler) {
        if(this.handlerAdapters.isEmpty()){return null;}
        return this.handlerAdapters.get(handler);
    }
    /**
     * 根据controller处理后返回的ModelAndView,进行解析，最终writer出去字符串结果。
     * 将静态的模板，转换成动态模板，类似模板引擎。
     * @param request
     * @param response
     * @param mv
     */
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
        //resolveViewName
        if(mv == null){return;}
        if(this.viewResolvers.isEmpty()){return;}

        for (ViewResolver viewResolver :viewResolvers) {
            if(!viewResolver.getViewName().equals(mv.getViewName())){continue;}
            String result = viewResolver.resolveViewName(mv);
            if(result != null){
                response.getWriter().write(result);
                break;
            }
        }
    }






}
